package com.leesche.netdoll.communication.protocol.encoder;

import java.net.URL;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.leesche.framework.core.codec.EndianUtil;
import com.leesche.framework.core.codec.HexUtil;
import com.leesche.framework.core.configuration.ConfigurationUtil;
import com.leesche.framework.core.json.GsonUtil;
import com.leesche.framework.core.lang.ArrayUtil;
import com.leesche.framework.core.lang.CollectionUtil;
import com.leesche.netdoll.communication.Settings;
import com.leesche.netdoll.communication.protocol.FrameType;
import com.leesche.netdoll.communication.protocol.MessageUtil;
import com.leesche.netdoll.communication.protocol.encoder.CommandConfiguration.ParameterConfiguration;

public class CommandManager {

	private static final Logger logger = LogManager.getLogger(CommandManager.class);

	private volatile static CommandManager instance;

	private static Map<String, CommandConfiguration> CMD_CONF;

	public static CommandManager newInstance() {
		if (instance == null) {
			synchronized (CommandManager.class) {
				if (instance == null)
					instance = new CommandManager();
			}
		}
		return instance;
	}

	private CommandManager() {
		initCommand();
	}

	private void initCommand() {
		logger.info("initializing command...");

		URL url = ConfigurationUtil.locateFromClasspath("command.json");

		CMD_CONF = GsonUtil.toMap(url, String.class, CommandConfiguration.class);

		logger.info("initializing command...OK");
	}

	public byte[] command(String commandId, Integer commandCode, FrameType frameType, int seq, Object... params) {
		CommandConfiguration cmdConf = CMD_CONF.get(commandId);

		byte[] pattern = cmdConf.pattern();
		List<ParameterConfiguration> paramConfs = cmdConf.getParameters();

		if (commandCode != null) {
			writeCommandCode(pattern, commandCode);
		}

		writeFrameType(pattern, frameType);
		writeSequence(pattern, seq);

		if (CollectionUtil.isNotEmpty(paramConfs) && ArrayUtil.isNotEmpty(params)) {
			writeParameters(pattern, paramConfs, params);
		}

		checksum(pattern);

		return pattern;
	}

	private static void checksum(final byte[] pattern) {

		if (ArrayUtil.isEmpty(pattern) || pattern.length < 11)
			return;

		pattern[4] = MessageUtil.crc8(ArrayUtil.subarray(pattern, 6, pattern.length));
		pattern[5] = MessageUtil.crc8(ArrayUtil.subarray(pattern, 0, 5));

	}

	public byte[] command(String commandId, FrameType frameType, int seq, Object... params) {
		return command(commandId, null, frameType, seq, params);
	}
/*
	public static void main(String[] args) {
		Settings.loadSettings();

		String a = "{\"deviceId\": \"d0bae41a4a7e\",\"gameId\":\"89EA2E4456DB49898349A7E7F5529EC4\",\"commandId\":\"START\",\"timestamp\":\"2017-11-10 17:59:29\",\"args\":[1,600,10,10,0]}";

		CommandData cd = GsonUtil.toBean(a, CommandData.class);

		byte[] cmd = CommandManager.newInstance().command("START", FrameType.START, 0, cd.getArgs());

		System.out.println(HexUtil.encodeHexString(cmd));
	}
*/
	public boolean contains(String commandId) {
		return CMD_CONF.containsKey(commandId);
	}

	private static void writeParameters(final byte[] pattern, final List<ParameterConfiguration> parameters, Object... params) {

		int idx = 0;
		for (ParameterConfiguration conf : parameters) {

			Encoder encoder = EncoderManager.newInstance().encoder(conf.getEncoder());

			if (encoder != null && params[idx] != null) {
				byte[] bparam = encoder.encode(params[idx]);

				int offset = conf.getOffset();
				int len = conf.getLen();

				System.arraycopy(bparam, 0, pattern, offset, len);
			}

			idx++;
		}
	}

	private static void writeFrameType(final byte[] pattern, final FrameType frameType) {
		pattern[8] |= ((frameType.getCode() << 6) & 0xc0);
	}

	private static void writeSequence(final byte[] pattern, final int seq) {

		byte[] seq_le = EndianUtil.encodeUnsignedShortLE(seq);
		pattern[9] = seq_le[0];
		pattern[10] = seq_le[1];
	}

	private static void writeCommandCode(final byte[] pattern, final Integer commandCode) {
		pattern[8] = (byte) (commandCode & 0xff);
	}
}
